package com.duolia.update;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.webkit.URLUtil;

import com.duolia.async.AsyncTask;
import com.duolia.bless.http.HttpClient;
import com.duolia.bless.utils.AndroidUtils;
import com.duolia.bless.utils.L;
import com.duolia.bless.utils.StringUtils;
import com.duolia.download.DownloadListener;
import com.duolia.download.DownloadManager;
import com.duolia.download.DownloadTask;

public class UpdateManager {
	
	private static final String TAG = UpdateManager.class.getSimpleName();

	/**
     * Start checking for update
     */
    protected static final int MSG_START = 0;
    /**
     * Finish checking for update
     */
    protected static final int MSG_FINISH = 1;
    /**
     * There is no need to update.
     */
    protected static final int MSG_SHOW_NO_UPDATE_UI = 2;
    /**
     * There is no need to update.
     */
    protected static final int MSG_SHOW_UPDATE_UI = 3;
    /**
     * There is no need to update.
     */
    protected static final int MSG_SHOW_UPDATE_PROGRESS_UI = 4;

    /**
     * click to update
     */
    protected static final int MSG_INFORM_UPDATE = 10;
    /**
     * click to cancel the update
     */
    protected static final int MSG_INFORM_CANCEL = 11;
    /**
     * click to skip the update
     */
    protected static final int MSG_INFORM_SKIP = 12;
    
    /**
     * error occurs
     */
    protected static final int MSG_ERROR = 6;
    private Context context = null;
    private AbstractUpdateListener mListener = null;
    private UpdateOptions options = null;
    private UIHandler handler = new UIHandler();
    private DownloadTask downloadTask = null;
    private String filepath;
    private String filename;
    private boolean isOverWrite = true;
   
    
    /**
     * install the apk
     *
     * @param context
     * @param filePath
     */
    private static void install(Context context, String filePath) {
        Intent i = new Intent(Intent.ACTION_VIEW);
        i.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android.package-archive");
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(i);
    }
    
    private UpdateManager() {
    }

    public UpdateManager(Context context) {
        this();
        this.context = context;
    }
    
    public void setFilePath(String filepath) {
		this.filepath = filepath;
	}
    
    public String getFilePath() {
		return filepath;
	}
    
    public void setFileName(String filename) {
		this.filename = filename;
	}
    
    public String getFileName() {
		return filename;
	}
    
    public void setOverWrite(boolean isOverWrite) {
		this.isOverWrite = isOverWrite;
	}
    
    public boolean isOverWrite() {
		return isOverWrite;
	}
    
    public boolean hasUpdate() {
    	return hasUpdate(null);
    }
    
    public boolean hasUpdate(Context context) {
    	if (context != null) {
            this.context = context;
        }
    	
    	PackageInfo pinfo;
		try {
			pinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
			Integer versionCode = pinfo.versionCode; 
			
			String PREFS_NAME = UpdateTips.getPrefsName(context);
			String PREFS_KEY_CHECK_UPDATE_VERSION_CODE = UpdateTips.getCheckUpdateVersionCode(context);
			SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
			String code = sp.getString(PREFS_KEY_CHECK_UPDATE_VERSION_CODE, "-1");
			if (Integer.parseInt(code) > versionCode)
				return true;
			
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}
		
    	
    	return false;
    }
    
    public void check(UpdateOptions options) {
    	check(null, options, null);
    }
    
    public void check(Context context, UpdateOptions options) {
        check(context, options, null);
    }
    
    public void check(Context context, UpdateOptions options, AbstractUpdateListener listener) {
        if (context != null) {
            this.context = context;
        }

        if (this.context == null) {
            L.w(TAG, "The Context is NUll!");
            handler.obtainMessage(MSG_ERROR, new UpdateException(UpdateException.CONTEXT_NOT_VALID)).sendToTarget();
            return;
        }

        if (listener == null) {
            listener = new DefaultUpdateListener();
        }
        this.mListener = listener;

        if (options == null) {
            L.w(TAG, "The UpdateOptions is NUll!");
            handler.obtainMessage(MSG_ERROR,  new UpdateException(UpdateException.UPDATE_OPTIONS_NOT_VALID)).sendToTarget();
            return;
        }
        this.options = options;

        //set the handler and the update options to the AbstractUpdateListener,etc
        listener.setContext(context);
        listener.setHandler(handler);
        listener.setUpdateOptions(options);

        if (options.shouldCheckUpdate()) {
        	
            AsycCheckUpdateTask checkUpdateTask = new AsycCheckUpdateTask(listener);
            checkUpdateTask.execute(options.getCheckUrl());
        } else {
            handler.obtainMessage(MSG_SHOW_NO_UPDATE_UI).sendToTarget();
        }
    }
    
	private class AsycCheckUpdateTask extends AsyncTask<String, Integer, UpdateInfo> {

		private HttpClient mHttpClient;
		private AbstractUpdateListener mListener;
		private AsycCheckUpdateTask() {
			mHttpClient = new HttpClient();
			mHttpClient.configTimeout(60 * 1000);
		}

		public AsycCheckUpdateTask(AbstractUpdateListener listener) {
			this();
			if (listener == null)
				listener = new DefaultUpdateListener();
			mListener = listener;
		}

		@Override
		protected UpdateInfo doInBackground(String... urls) {
			handler.obtainMessage(MSG_START).sendToTarget();
			if (urls.length <= 0) {
                L.e(TAG, "There is no url.");
                handler.obtainMessage(MSG_SHOW_NO_UPDATE_UI).sendToTarget();
                return null;
            }

            String url = urls[0];
            if (!URLUtil.isNetworkUrl(url)) {
            	L.e(TAG, "There is no url.");
                handler.obtainMessage(MSG_SHOW_NO_UPDATE_UI).sendToTarget();
                return null;
            }
            
            UpdateInfo info = null;
            String xml = null;
            String json = null;
            String result = (String) mHttpClient.getSync(url);
            switch (options.getUpdateFormat()) {
            	case XML:
					try {
						xml = result;
	           		 UpdateXmlParser xmlParser = new UpdateXmlParser();
						info = xmlParser.parse(xml);
					} catch (UpdateException e) {
						e.printStackTrace();
						 L.e("UpdateException", e.getLocalizedMessage());
					}
            		break;
            	case JSON:
            	default:
					try {
						json = result;
	           		 	UpdateJsonParser jsonParser = new UpdateJsonParser();
						info = jsonParser.parse(json);
					} catch (UpdateException e) {
						e.printStackTrace();
						L.e("UpdateException", e.getLocalizedMessage());
					}
            		break;
            }
			return info;
		}

		@Override
		protected void onPostExecute(UpdateInfo updateInfo) {
			 super.onPostExecute(updateInfo);
			 handler.obtainMessage(MSG_FINISH).sendToTarget();
			 if (context != null && updateInfo != null) {
				try {
					PackageInfo pinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
					Integer versionCode = pinfo.versionCode; // 1
					String versionName = pinfo.versionName; // 1.0
					String packageName = context.getPackageName();

					if (options.shouldCheckPackageName() && !packageName.equals(updateInfo.getPackageName())) {
						((AbstractUpdateListener) mListener).onShowNoUpdateUI();
						return;
					}
					String PREFS_NAME = UpdateTips.getPrefsName(context);
					String PREFS_KEY_SKIP_CHECK_UPDATE_VERSION_CODE = UpdateTips.getSkipCheckUpdateVersionCode(context);
					SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
					String skip_version_code = sp.getString(PREFS_KEY_SKIP_CHECK_UPDATE_VERSION_CODE, "-1");

					if (!StringUtils.isBlank(updateInfo.getVersionCode()) && Integer.parseInt(updateInfo.getVersionCode()) > versionCode) {
						if (!updateInfo.isForceUpdate() && !options.shouldForceUpdate() && skip_version_code.equalsIgnoreCase(updateInfo.getVersionCode()))
						{
							((AbstractUpdateListener) mListener).onShowNoUpdateUI();
						} else {
							
							String PREFS_KEY_CHECK_UPDATE_VERSION_CODE = UpdateTips.getCheckUpdateVersionCode(context);
							sp.edit().putString(PREFS_KEY_CHECK_UPDATE_VERSION_CODE, updateInfo.getVersionCode());
							if (options.shouldAutoUpdate() || updateInfo.isAutoUpdate()) {
								informUpdate(updateInfo);
							} else {
								((AbstractUpdateListener) mListener).onShowUpdateUI(updateInfo);
							}
						}
					} else {
						((AbstractUpdateListener) mListener).onShowNoUpdateUI();
					}
				} catch (PackageManager.NameNotFoundException e) {
					e.printStackTrace();
					L.e("can not get the package info", e.getLocalizedMessage());
				}
			 }
		}
	}
    
    /**
     * user click to confirm the update
     */
    private void informUpdate(final UpdateInfo info) {
        if (info == null) {
            return;
        }

        DownloadManager manager = new DownloadManager(context);

        downloadTask = new DownloadTask(context);
        downloadTask.setUrl(info.getApkUrl());
        downloadTask.setPath(filepath);
        downloadTask.setName(filename);
        downloadTask.setOverWrite(isOverWrite);

        manager.start(downloadTask, new DownloadListener() {
            @Override
            public void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);
                handler.obtainMessage(MSG_SHOW_UPDATE_PROGRESS_UI, values[0], -1,info).sendToTarget();
            }

            @Override
            public void onSuccess(DownloadTask downloadTask) {
                super.onSuccess(downloadTask);
                if (downloadTask != null && !TextUtils.isEmpty(downloadTask.getPath())) {
                    install(context, downloadTask.getPath());
                    caculateNextTime(null, false);
                }
            }
        });
    }
    
    /**
     * caculate the next time to update
     */
    private void caculateNextTime(UpdateInfo info, boolean isCancel) {
        long now = System.currentTimeMillis();

        
        String PREFS_NAME = UpdateTips.getPrefsName(context);
        String PREFS_KEY_NEXT_CHECK_UPDATE_TIME = UpdateTips.getNextCheckUpdateTime(context);
        String PREFS_KEY_SKIP_CHECK_UPDATE_VERSION_CODE = UpdateTips.getSkipCheckUpdateVersionCode(context);
        SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();

        long period = 0;
        UpdatePeriod updatePeriod = options.getUpdatePeriod();
        if (updatePeriod != null) {
            period = updatePeriod.getPeriodMillis();
        }
        if (period > 0) {
        	long next = now + period;
            editor.putLong(PREFS_KEY_NEXT_CHECK_UPDATE_TIME, next);
        }
        if (info != null && !isCancel) {
            editor.putString(PREFS_KEY_SKIP_CHECK_UPDATE_VERSION_CODE, info.getVersionCode());
        }
        editor.commit();
    }
    
    
    /**
     * user click to cancel the update
     */
    private void informCancel(final UpdateInfo info) {
    	if (info == null) {
            return;
        }

        if (!options.shouldForceUpdate() && !info.isForceUpdate()) {
            caculateNextTime(info, true);
        }
    }

    /**
     * user click to skip the update
     */
    private void informSkip(final UpdateInfo info) {
        if (info == null) {
            return;
        }

        if (!options.shouldForceUpdate() && !info.isForceUpdate()) {
            caculateNextTime(info, false);
        }
    }
    
    private class UIHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_START:
                	mListener.onStart();
                    break;
                case MSG_ERROR:
                    if (msg.obj != null) {
                    	mListener.onError((Throwable) msg.obj);
                    }
                    break;
                case MSG_FINISH:
                	mListener.onFinish();
                    break;
                case MSG_SHOW_NO_UPDATE_UI:
                	mListener.onShowNoUpdateUI();
                    break;
                case MSG_SHOW_UPDATE_PROGRESS_UI:
                    if (downloadTask != null) {
                    	mListener.onShowUpdateProgressUI((UpdateInfo) msg.obj,downloadTask, msg.arg1);
                    }
                    break;
                case MSG_INFORM_UPDATE:
                    informUpdate((UpdateInfo) msg.obj);
                    break;
                case MSG_INFORM_CANCEL:
                    informCancel((UpdateInfo) msg.obj);
                    break;
                case MSG_INFORM_SKIP:
                    informSkip((UpdateInfo) msg.obj);
                    break;
                default:
                    break;
            }
        }
    }
    
}
